#include <kutils.h>
#include "memory.h"
#include "paging.h"
#include "heap.h"
#include "display.h"
#include "multiboot.h"

void mem_frame_alloc(u32int addr);
void mem_frame_free(u32int addr);
s32int mem_frame_is_used(u32int addr);
void mem_randomize(u32int seed);

extern u32int page_size;
extern address_space_t* active_space;
extern u32int end;

u32int free_mem_end;
u32int free_mem_start = (u32int)&end;
u32int mem_features = MEM_FEATURES_NONE;
heap_t *kheap;

u32int *mem_frames;
u32int num_frames;

//marks a frame of physical memory as allocated
void mem_frame_alloc(u32int addr)
{
	//write_char('a');

	if(!(mem_features & MEM_FEATURES_PHYS))
		return;

	u32int frame_n = addr / page_size;
	mem_frames[frame_n / 32] |= 0x1 << (frame_n % 32);
}

//marks a frame of physical memory as free
void mem_frame_free(u32int addr)
{
	if(!(mem_features & MEM_FEATURES_PHYS))
		return;

	u32int frame_n = addr / page_size;

	mem_frames[frame_n / 32] &= ~(0x1 << (frame_n % 32));//the "~" is the bitwise not operator
}

//returns one if a physical frame of memory is allocated. Zero if it is free.
s32int mem_frame_is_used(u32int addr)
{
	//write_char('c');

	if(!(mem_features & MEM_FEATURES_PHYS))
		return 0;

	u32int frame_n = addr / page_size;
	return (mem_frames[frame_n / 32] & (0x1 << (frame_n % 32))) ? 1 : 0;//only returns one or zero
}

void mem_randomize(u32int seed)
{
	#ifdef FLUIDIUM_DEBUG
		write_string("Randomizing free memory (for testing purposes)...\n");
	#endif
	seed++;
	u32int* loc = (u32int*)free_mem_start;
	while((u32int)loc < free_mem_end - 4)
	{
		*loc = 0xFFFFFFFF;
		//write_hex(seed);
		//*loc = ((seed * seed) | 0x23242526) >> 3;
		//seed = *loc;
		loc++;
	}
}

void init_memory(u32int mem_size_bytes, multiboot_info_t* mbd)
{
#ifdef FLUIDIUM_DEBUG
	write_string("Memory: Init\n");
	write_string("Memory: Found ");
	write_number(mem_size_bytes / 1024);
	write_string(" kilobytes (");
	write_number((mem_size_bytes / 1024) / 1024);
	write_string(" megabytes) of ram.\n");
#endif

	u32int i = 0;
	free_mem_end = mem_size_bytes;

	//mem_randomize(0x62341254);//simulates the uncertian state of memory on a real computer. 

	initialize_paging();
	//it is important to start paging here before loading the bitmap physical memory allocator.
	//the nature of the basic watermark allocator (used before the bitmap allocator)
	//makes it easier to initalize paging.

	num_frames = mem_size_bytes / page_size;

	mem_frames = (u32int*)id_page_alloc(sizeof(u32int) * (num_frames / 32));//uses the watermark allocator at this pos32int.
	mem_features |= MEM_FEATURES_PHYS;

	//mark all memory as used...
	memset(mem_frames, (s32int)0xFFFFFFFF, (size_t)(num_frames / 32));
	free_mem_start = (free_mem_start & 0xFFFFF000) + page_size;

	//fill in the memory map based on the information passed from the BIOS to GRUB and then to the Kernel
	memory_map_t* map = (memory_map_t*)mbd->mmap_addr;
	while((u32int)map < mbd->mmap_addr + mbd->mmap_length)
	{
		if(map->type == MEM_TYPE_USEABLE)
		{
			for(i = map->base_addr_low; i < map->base_addr_low + map->length_low; i += page_size)
			{
				phys_free(i);
			}
		}
		map = (memory_map_t*)((u32int)map + map->size + sizeof(u32int));
	}
	//...then mark all of lower memory up through free_mem_start as used
	for(i = 0; i < free_mem_start; i += page_size)
	{
		mem_frame_alloc(i);
	}
	kheap = create_heap(0x10000000, 0x10002000);//create a 4 kilobyte heap (it will expand as needed)
}

u32int kmalloc(u32int size)
{
	if(mem_features & MEM_FEATURES_HEAP)
	{
		return heap_alloc(kheap, size);
	}
	else
	{
		return 0;
	}
}

//returns the physical starting address of a free chunck of physical memory of the given size.
u32int phys_alloc(u32int size)
{
	if(mem_features & MEM_FEATURES_PHYS)
	{//this means that we have a physical memory manager working
		u32int i, j = 0;
		u32int free_start = 0;
		u32int free_length = 0;

		if(size > free_mem_end)
		{//not enough memory
			write_string("Memory: Request for ");
			write_number(size);
			write_string(" bytes is more memory than is installed in the system!\n");
			return 0;
		}

		for(i = 0; i < free_mem_end; i += page_size)
		{
			if(!mem_frame_is_used(i))
			{//the frame is free				
				if(free_length == 0)
				{//we need to start keeping track of a new set of free frames
					free_start = i;
					free_length += page_size;
				}
				else
				{//we are alredy keeping track of a set of free frames. just make the set longer.
					free_length += page_size;
				}

				if(free_length >= size)
				{//we found a free set of the required size
					for(j = free_start; j < free_start + free_length; j += page_size)//allocate all of the required frames
					{
						mem_frame_alloc(j);
					}
					//return the starting physical address
					return free_start;
				}
				//if we get here it means that the free set isn't big enough yet. 
				//It may become big enough if we find out that subsiquent frames are also free.
			}
			else
			{//the frame is taken. 
			//This means that the set is not big enough (we found an allocated 
			//frame before the set was big enough) and we need to search for a larger set.
				free_start = 0;
				free_length = 0;
			}
		}
		//if we get here it means that there is no free chunck of memory large enough.
		write_string("Memory: Out of physical memory!\n");
		return 0;
	}
	else
	{//no physical memory manager yet, just use a placement address manager.
		//get the beginning of free memory
		u32int address = free_mem_start;

		//modify the address to be page aligned
		if(address & 0x00000FFF)
		{
			address += page_size;
			address &= 0xFFFFF000;
		}
 
		//move the beginning of free memory past the newly allocated area
		free_mem_start = address + size;
		//return the address of the new area
		return address;
	}
}

void kfree(void *p)
{
	heap_free(kheap, p);
}

s32int phys_free(u32int addr)
{
	if(!(mem_features & MEM_FEATURES_PHYS))
		return 0;

	if(addr >= free_mem_end)
		return 0;

	mem_frame_free(addr);
	return 1;
}
